
package com.samh.openrower2;

import com.samh.openrower2.util.LowpassFilter;



public class HalfSinoidDetector {
	private static final float DEFAULT_AMPLITUDE_CHANGE_DAMP_FACTOR = .025f;
	private static final float DEFAULT_AMPLITUDE_CHANGE_ACCEPT_FACTOR = .025f;
	private static final float DEFAULT_MIN_AMPLITUDE = .1f;
	

	enum State {
		NONE,
		ENTER,
		TESHOLD_PASS,
		VALID_EXIT,
		INVALID_EXIT		
	}
	
	private State state = State.NONE;
	
	/**
	 * Identifies either this object is to detect above zero 'UP' or
	 * below zero 'DOWN' half of the stroke acceleration sinoid
	 */
	enum Dir {
		UP,
		DOWN
	}
	
	public final Dir dir;
	
	private float amplitudeChangeAcceptFactor;
	private float amplitudeDampedValue;
	private float minAmplitude;
	float maxVal;
	private final LowpassFilter amplitudeChangeDamper = new LowpassFilter();
	private boolean wasInside;
	
	HalfSinoidDetector(Dir dir) {
		this.dir = dir;
		
		setMinAmplitude(DEFAULT_MIN_AMPLITUDE);
		setAmplitudeChangeDamperFactor(DEFAULT_AMPLITUDE_CHANGE_DAMP_FACTOR);
		setAmplitudeChangeAcceptFactor(DEFAULT_AMPLITUDE_CHANGE_ACCEPT_FACTOR);
	}

	public float getAmplitudeChangeAcceptFactor() {
		return amplitudeChangeAcceptFactor;
	}

	public void setAmplitudeChangeAcceptFactor(float amplitudeChangeAcceptFactor) {
		this.amplitudeChangeAcceptFactor = amplitudeChangeAcceptFactor;
	}

	public float getMinAmplitude() {
		return minAmplitude;
	}

	public void setMinAmplitude(float minAmplitude) {
		this.minAmplitude = minAmplitude;
	}

	public float getAmplitudeChangeDamperFactor() {
		return amplitudeChangeDamper.getFilteringFactor();
	}

	public void setAmplitudeChangeDamperFactor(float factor) {
		amplitudeChangeDamper.setFilteringFactor(factor);
	}

	/**
	 * checks either the acceleration value is inside the jurisdiction of this object
	 * @param v acceleration value
	 * @return true if insde, false if not
	 */
	private boolean checkInside(float v) {
		switch (dir) {
		case DOWN:
			if (v < 0) {
				return true;
			}
			break;
		case UP:
			if (v > 0) {
				return true;
			}
			break;			
		}
		return false;
	}
	
	/**
	 * add acceleration value and return true if a stroke was detected
	 * @param v stroke acceleration value
	 * @return true if this added acceleration value <code>v</code> causes a detection of a stroke
	 */
	State add(float v) {
		State retval = State.NONE;
		
		if (checkInside(v)) {
			
			if (!wasInside) {
				maxVal = 0;
			}
			
			maxVal = Math.max(Math.abs(v), maxVal);
			wasInside = true;
			
			switch (state) {
			case NONE:
				state = State.ENTER;
				/* no break */
			case ENTER:
				if (maxVal > minAmplitude && maxVal > amplitudeDampedValue * amplitudeChangeAcceptFactor) {
					state = State.TESHOLD_PASS;
					retval = State.TESHOLD_PASS;
				}
				break;
			}
			
		} else if (wasInside) { 
			
			amplitudeDampedValue = amplitudeChangeDamper.filter(new float[]{maxVal})[0];
			
			switch (state) {
			case TESHOLD_PASS:
				retval = State.VALID_EXIT;
				break;
			default:
				retval = State.INVALID_EXIT;
				break;
			}
			
			state = State.NONE;

			wasInside = false;
			
						
		}
				
		return retval;
	}

}
